﻿using InTheHand.Net;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace OBDTest
{
	public partial class MainForm : Form
	{
		protected log4net.ILog LogFile = log4net.LogManager.GetLogger("LogInfo");

		BluetoothClient m_Device = null;
		NetworkStream m_Stream = null;
		bool m_bRun = false;
		private object m_Lock = new object();
		Thread m_WorkThread = null;

		long m_nDeviceAddress = 127329147018;               // OBDII

		public MainForm()
		{
			log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(AppDomain.CurrentDomain.BaseDirectory + "Log4Net.config"));

			InitializeComponent();
		}

		private void MainForm_Load(object sender, EventArgs e)
		{
			BluetoothRadio bluetoothRadio = BluetoothRadio.PrimaryRadio;
			if (bluetoothRadio == null)
			{
				LogFile.Error("没有找到本机蓝牙设备!");
			}
			else
			{
				LogFile.Debug("本机蓝牙设备正常。");
			}

			m_bRun = true;
			m_WorkThread = new Thread(DoWork);
			m_WorkThread.Start();
		}

		delegate void UpdateStatus_Delegate(string sStatus);
		private void UpdateStatus(string sStatus)
		{
			if (InvokeRequired)
			{
				UpdateStatus_Delegate func = new UpdateStatus_Delegate(UpdateStatus);
				Invoke(func, sStatus);
			}
			else
			{
				L_Status.Text = sStatus;
			}
		}

		delegate void UpdateWorkStatus_Delegate(int nIndex);
		private void UpdateWorkStatus(int nIndex)
		{
			if (InvokeRequired)
			{
				UpdateWorkStatus_Delegate func = new UpdateWorkStatus_Delegate(UpdateWorkStatus);
				Invoke(func, nIndex);
			}
			else
			{
				string sStatus = "工作中";
				for (int i = 0; i < nIndex; i++)
					sStatus += ".";

				L_WorkStatus.Text = sStatus;
			}
		}

		delegate void UpdateRPM_Delegate(int nRpm);
		private void UpdateRPM(int nRpm)
		{
			if (InvokeRequired)
			{
				UpdateRPM_Delegate func = new UpdateRPM_Delegate(UpdateRPM);
				Invoke(func, nRpm);
			}
			else
			{
				L_RPM.Text = nRpm.ToString();
			}
		}

		delegate void UpdateSpeed_Delegate(int nSpeed);
		private void UpdateSpeed(int nSpeed)
		{
			if (InvokeRequired)
			{
				UpdateSpeed_Delegate func = new UpdateSpeed_Delegate(UpdateSpeed);
				Invoke(func, nSpeed);
			}
			else
			{
				L_Speed.Text = nSpeed.ToString();
			}
		}

		delegate void UpdateCoolantTemp_Delegate(int nCoolantTemp);
		private void UpdateCoolantTemp(int nCoolantTemp)
		{
			if (InvokeRequired)
			{
				UpdateCoolantTemp_Delegate func = new UpdateCoolantTemp_Delegate(UpdateCoolantTemp);
				Invoke(func, nCoolantTemp);
			}
			else
			{
				L_CoolantTemp.Text = nCoolantTemp.ToString();
			}
		}

		delegate void UpdateIntakeAirTemp_Delegate(int nIntakeAirTemp);
		private void UpdateIntakeAirTemp(int nIntakeAirTemp)
		{
			if (InvokeRequired)
			{
				UpdateIntakeAirTemp_Delegate func = new UpdateIntakeAirTemp_Delegate(UpdateIntakeAirTemp);
				Invoke(func, nIntakeAirTemp);
			}
			else
			{
				L_IntakeAirTemp.Text = nIntakeAirTemp.ToString();
			}
		}

		private bool ConnectBT()
		{
			try
			{
				BluetoothAddress address = new BluetoothAddress(m_nDeviceAddress);

				m_Device = new BluetoothClient();
				//m_Device.SetPin("1234");
				m_Device.Connect(address, BluetoothService.SerialPort);

				LogFile.DebugFormat("Connected OBD device: {0}", m_Device.Connected);

				if (!m_Device.Connected)
					return false;

				m_Stream = m_Device.GetStream();
				m_Stream.ReadTimeout = 5000;

				return true;
			}
			catch (Exception ex)
			{
				LogFile.ErrorFormat("Connect OBD device failed.\r\n{0}", ex.Message);
				return false;
			}
		}

		private void DisconnectBT()
		{
			if (m_Stream != null)
			{
				m_Stream.Close();
				m_Stream = null;
			}

			if (m_Device != null)
			{
				m_Device.Close();
				m_Device = null;
			}
		}

		private bool SendCmd(string sCmd, out string sResponse)
		{
			sResponse = "ERROR";

			try
			{
				string sCmdTmp = sCmd + "\r";
				byte[] bCmd = Encoding.ASCII.GetBytes(sCmdTmp + "\r");
				byte[] buffer = new byte[1024];
				int nBytes = 0;

				lock (m_Lock)
				{
					LogFile.DebugFormat("S: {0}", sCmd);

					m_Stream.Write(bCmd, 0, bCmd.Length);
					while (true)
					{
						Thread.Sleep(100);
						nBytes += m_Stream.Read(buffer, nBytes, 1024 - nBytes);
						sResponse = Encoding.ASCII.GetString(buffer, 0, nBytes);
						if (sResponse.IndexOf('>') != -1)
							break;
						//LogFile.DebugFormat("Read {0}, will continue to read...", sResult);
					}
				}

				//LogFile.DebugFormat("R0: {0}", sResponse);

				sResponse = sResponse.TrimEnd('>');
				sResponse = sResponse.TrimEnd();
				LogFile.DebugFormat("R: {0}", sResponse);

				return true;
			}
			catch (Exception ex)
			{
				LogFile.ErrorFormat("Send cmd failed.\r\n{0}", ex.Message);
				return false;
			}
		}

		private bool SendOBDCmd(string sCmd, out string sResponse)
		{
			sResponse = "ERROR";

			try
			{
				string sCmdTmp = sCmd + "\r";
				byte[] bCmd = Encoding.ASCII.GetBytes(sCmdTmp + "\r");
				byte[] buffer = new byte[1024];
				int nBytes = 0;

				lock (m_Lock)
				{
					LogFile.DebugFormat("S: {0}", sCmd);

					m_Stream.Write(bCmd, 0, bCmd.Length);
					while (true)
					{
						Thread.Sleep(100);
						nBytes += m_Stream.Read(buffer, nBytes, 1024 - nBytes);
						sResponse = Encoding.ASCII.GetString(buffer, 0, nBytes);
						if (sResponse.IndexOf('>') != -1)
							break;
						//LogFile.DebugFormat("Read {0}, will continue to read...", sResult);
					}
				}

				//LogFile.DebugFormat("R0: {0}", sResponse);

				sResponse = sResponse.TrimEnd('>');
				sResponse = sResponse.TrimEnd();
				LogFile.DebugFormat("R: {0}", sResponse);

				if (sResponse.Length < 4 || sResponse[0] != '4' || sResponse.Substring(1, 3) != sCmd.Substring(1,3))
				{
					LogFile.Error("OBD cmd response is not expected.");
					return false;
				}

				return true;
			}
			catch (Exception ex)
			{
				LogFile.ErrorFormat("Send cmd failed.\r\n{0}", ex.Message);
				return false;
			}
		}

		private bool TryCommunicateECU()
		{
			string sResponse;

			if (!SendCmd("0100", out sResponse))
				return false;

			if (sResponse.Length <= 4 || sResponse.IndexOf("4100") == -1)
				return false;

			if (!SendOBDCmd("0100", out sResponse))
				return false;

			return true;
		}

		private bool ConnectOBD()
		{
			string sResponse;

			if (!SendCmd("ATZ", out sResponse))
				return false;
			if (sResponse.IndexOf("ELM327") == -1)
			{
				LogFile.Error("Resopnse is not expected.");
				return false;
			}

			if (!SendCmd("ATE0", out sResponse))
				return false;

			if (!SendCmd("ATE0", out sResponse))
				return false;
			if (sResponse != "OK")
			{
				LogFile.Error("Resopnse is not expected.");
				return false;
			}

			if (!SendCmd("ATL0", out sResponse))
				return false;

			if (!SendCmd("ATS0", out sResponse))
				return false;

			if (!SendCmd("ATI", out sResponse))
				return false;

			if (!SendCmd("ATH0", out sResponse))
				return false;

			if (!SendCmd("ATAT1", out sResponse))
				return false;

			if (!SendCmd("ATDPN", out sResponse))
				return false;
			bool bCommunicateOBDOK = false;
			for (int i = 0; i < 10; i++)
			{
				if (TryCommunicateECU())
				{
					bCommunicateOBDOK = true;
					break;
				}

				LogFile.DebugFormat("Communicate OBD failed, will try A{0}.", i.ToString("X"));

				if (!SendCmd(string.Format("ATSPA{0}", i.ToString("X")), out sResponse))
					return false;
			}

			if (!bCommunicateOBDOK)
			{
				// Try A0 again.
				if (!SendCmd("ATSPA0", out sResponse))
					return false;

				if (!TryCommunicateECU())
					return false;
			}

			return true;
		}

		private bool GetRPM(out int nRpm)
		{
			nRpm = 0;
			string sResponse;

			if (!SendOBDCmd("010C", out sResponse))
				return false;

			string sData = sResponse.Substring(4);
			nRpm = Convert.ToInt32(sData, 16) / 4;

			return true;
		}

		private bool GetSpeed(out int nSpeed)
		{
			nSpeed = 0;
			string sResponse;

			if (!SendOBDCmd("010D", out sResponse))
				return false;

			string sData = sResponse.Substring(4);
			nSpeed = Convert.ToInt32(sData, 16);

			return true;
		}

		private bool GetCoolantTemp(out int nCoolantTemp)
		{
			nCoolantTemp = 0;
			string sResponse;

			if (!SendOBDCmd("0105", out sResponse))
				return false;

			string sData = sResponse.Substring(4);
			nCoolantTemp = Convert.ToInt32(sData, 16) - 40;

			return true;
		}

		private bool GetIntakeAirTemp(out int nIntakeAirTemp)
		{
			nIntakeAirTemp = 0;
			string sResponse;

			if (!SendOBDCmd("010F", out sResponse))
				return false;

			string sData = sResponse.Substring(4);
			nIntakeAirTemp = Convert.ToInt32(sData, 16) - 40;

			return true;
		}

		private bool GetVIN(out string sVIN)
		{
			sVIN = "NA";
			string sResponse;

			if (!SendOBDCmd("0902", out sResponse))
				return false;

			//sResponse = "4902010000004C\r49020246563241\r49020331313533\r49020442333031\r49020535303236";
			string sVINData = sResponse.Remove(0, 12);
			sVINData = sVINData.Remove(2, 7);
			sVINData = sVINData.Remove(10, 7);
			sVINData = sVINData.Remove(18, 7);
			sVINData = sVINData.Remove(26, 7);

			//string[] sLines = sResponse.Split('\r');
			//if (sLines.Length != 5)
			//	return false;

			//sVINData += sLines[0].Substring(12);
			//sVINData += sLines[1].Substring(6);
			//sVINData += sLines[2].Substring(6);
			//sVINData += sLines[3].Substring(6);
			//sVINData += sLines[4].Substring(6);

			//LogFile.Debug(sVINData);

			byte[] bVIN = new byte[sVINData.Length / 2];
			string sTmp;
			for (int i = 0; i < bVIN.Length; i++)
			{
				sTmp = sVINData.Substring(i * 2, 2);
				bVIN[i] = Convert.ToByte(sTmp, 16);
			}

			sVIN = Encoding.ASCII.GetString(bVIN, 0, bVIN.Length);
			LogFile.DebugFormat("VIN: {0}", sVIN);

			return true;
		}

		private void DoWork()
		{
			UpdateStatus("连接设备...");
			if (!ConnectBT())
			{
				UpdateStatus("连接设备失败");
				return;
			}
			UpdateStatus("连接设备成功");

			bool bConnectOBDOK = false;
			for (int i = 0; i < 5; i++)
			{
				if (ConnectOBD())
				{
					bConnectOBDOK = true;
					break;
				}
			}

			if (bConnectOBDOK)
				UpdateStatus("与车辆连接成功");
			else
			{
				UpdateStatus("与车辆连接失败");
				return;
			}

			string sResponse = "";
			int nTime1 = 0;
			int nTime2 = 0;
			bool bNeedRetry = false;
			int nTmp = 0;

			while (m_bRun)
			{
				Thread.Sleep(100);
				nTime1++;
				if (nTime1 / 10 != nTime2)
				{
					nTime2 = nTime1 / 10;
					if (nTime2 >= 10)
						nTime1 = 0;
				}
				UpdateWorkStatus(nTime2);

				if (!GetRPM(out nTmp))
				{
					bNeedRetry = true;
					break;
				}
				UpdateRPM(nTmp);

				if (!GetSpeed(out nTmp))
				{
					bNeedRetry = true;
					break;
				}
				UpdateSpeed(nTmp);

				if (!GetCoolantTemp(out nTmp))
				{
					bNeedRetry = true;
					break;
				}
				UpdateCoolantTemp(nTmp);

				if (!GetIntakeAirTemp(out nTmp))
				{
					bNeedRetry = true;
					break;
				}
				UpdateIntakeAirTemp(nTmp);
			}

			if (bNeedRetry)
			{
				DisconnectBT();
				m_WorkThread = new Thread(DoWork);
				m_WorkThread.Start();
			}
		}

		private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
		{
			if (m_WorkThread != null)
			{
				m_bRun = false;
				for (int i = 0; i < 10; i++)
				{
					if (!m_WorkThread.IsAlive)
						break;

					Thread.Sleep(100);
				}
				if (m_WorkThread.IsAlive)
					m_WorkThread.Abort();
			}

			DisconnectBT();
		}

		private void Btn_GetVIN_Click(object sender, EventArgs e)
		{
			string sVIN;

			GetVIN(out sVIN);

			L_VIN.Text = sVIN;
		}
	}
}
